package edu.cn.nlsde.tmfst.lda;

import edu.cn.nlsde.Utils.IOUtil;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;

/**
 * Created by dell-pc on 2016/4/1.
 */

public class LDA_inference {
    private final String data_path;
    private final double alpha;
    private final double beta;
    private final int iter_num;
    private final String model_dir;
    private final String outputPath;
    private int topic_num;
    private HashMap<String, Integer> wordMap = new HashMap<>();
    private HashMap<Integer, String> id2Word = new HashMap<>();

    private int[][] wordId_of_corpus = null;
    private int[][] topicId_of_corpus = null;

    private double[][] phi = null;

    private int[][] word_num_in_doc_topic = null;
    private int[] word_num_in_doc = null;


    public LDA_inference(String data_path, String model_dir, int iter_num, double alpha, double beta, String prefix) {

        this.alpha = alpha;
        this.beta = beta;
        this.iter_num = iter_num;
        this.data_path = data_path;
        this.model_dir = model_dir;
        this.outputPath = (new File(data_path)).getParentFile().getAbsolutePath() + "/" + prefix + "_infer_" + alpha + "_" + beta + "/";
        (new File(this.outputPath)).mkdirs();
        print_parameter();
    }

    public static String infer_LDA(String data_path, String model_dir, int iter_num, double alpha, double beta, String prefix) {

        LDA_inference model = new LDA_inference(data_path, model_dir, iter_num, alpha, beta, prefix);
        model.run();
        return model.outputPath + "model-final.tassign";

    }

    public static String infer_LDA(String data_path, String model_dir, int iter_num, double alpha, double beta) {
        String prefix = "LDA";

        LDA_inference model = new LDA_inference(data_path, model_dir, iter_num, alpha, beta, prefix);
        model.run();
        return model.outputPath + "model-final.tassign";

    }

    private void print_parameter() {
        System.out.println("path:" + this.data_path
                + "\talpha:" + this.alpha
                + "\tbeta:" + this.beta
                + "\tmodel_dir:" + this.model_dir
                + "\ttopic_num:" + this.topic_num
        );
    }

    private void load_data() {
        try {
            BufferedReader reader = IOUtil.getReader(this.data_path);
            String line;
            ArrayList<int[]> tmpCorpus = new ArrayList<>();
            while ((line = reader.readLine()) != null) {
                String[] words = line.split("\\s+");
                int[] oneDoc = new int[words.length];
                for (int wordIndex = 0; wordIndex < words.length; wordIndex++) {
                    if (this.wordMap.containsKey(words[wordIndex])) {
                        oneDoc[wordIndex] = this.wordMap.get(words[wordIndex]);
                    }
                }
                tmpCorpus.add(oneDoc);
            }
            reader.close();

            this.wordId_of_corpus = new int[tmpCorpus.size()][];
            for (int docIndex = 0; docIndex < this.wordId_of_corpus.length; docIndex++) {
                this.wordId_of_corpus[docIndex] = tmpCorpus.get(docIndex);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    private void load_worMap() {
        try {
            BufferedReader reader = IOUtil.getReader(this.model_dir + "model-final.wordmap");
            String line;
            while ((line = reader.readLine()) != null) {
                String[] words = line.split("\\s+");
                this.wordMap.put(words[0], Integer.parseInt(words[1]));
                this.id2Word.put(Integer.parseInt(words[1]), words[0]);
            }
            reader.close();

        } catch (Exception e) {
            e.printStackTrace();
        }

    }

//    private void save_twords(int topWordNum) throws IOException {
//        BufferedWriter writer = IOUtil.getWriter(this.outputPath + "model-final.twords");
//
//        for (int topic_index = 0; topic_index < this.word_num_in_topic_word.length; topic_index++) {
//            HashMap<Integer, Double> oneLine = new HashMap<>();
//            for (int word_id = 0; word_id < this.word_num_in_topic_word[topic_index].length; word_id++) {
//                oneLine.put(word_id, ((double) this.word_num_in_topic_word[topic_index][word_id]) / this.word_num_in_topic[topic_index]);
//            }
//            List<Map.Entry<Integer, Double>> maplist = new ArrayList<>(oneLine.entrySet());
//
//            Collections.sort(maplist, (o1, o2) -> o2.getValue().compareTo(o1.getValue()));
//
//            writer.write("Topic:" + topic_index + "\n");
//            int count = 0;
//            for (Map.Entry<Integer, Double> o1 : maplist) {
//                writer.write("\t" + this.id2Word.get(o1.getKey()) + "\t:" + o1.getValue() + "\n");
//                count++;
//                if (count > topWordNum) {
//                    break;
//                }
//            }
//            writer.write("\n");
//
//        }
//
//
//        writer.close();
//    }

    private void load_phi() {
        try {
            BufferedReader reader = IOUtil.getReader(this.model_dir + "model-final.phi");
            String line;
            ArrayList<double[]> tmpPhi = new ArrayList<>();
            while ((line = reader.readLine()) != null) {
                String[] words = line.split("\\s+");
                double[] oneTopic = new double[words.length];
                for (int wordIndex = 0; wordIndex < words.length; wordIndex++) {
                    oneTopic[wordIndex] = Double.parseDouble(words[wordIndex]);
                }
                tmpPhi.add(oneTopic);
            }
            reader.close();
            this.topic_num = tmpPhi.size();
            this.phi = new double[tmpPhi.size()][];
            for (int TopicIndex = 0; TopicIndex < this.phi.length; TopicIndex++) {
                this.phi[TopicIndex] = tmpPhi.get(TopicIndex);
            }

        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    private void init_model() {
        this.topicId_of_corpus = new int[this.wordId_of_corpus.length][];

        this.word_num_in_doc_topic = new int[this.wordId_of_corpus.length][this.topic_num];
        this.word_num_in_doc = new int[this.wordId_of_corpus.length];


        for (int docIndex = 0; docIndex < this.wordId_of_corpus.length; docIndex++) {
            this.topicId_of_corpus[docIndex] = new int[this.wordId_of_corpus[docIndex].length];
            this.word_num_in_doc[docIndex] = this.wordId_of_corpus[docIndex].length;
            for (int wordIndex = 0; wordIndex < this.wordId_of_corpus[docIndex].length; wordIndex++) {
                int topicId = (int) (Math.random() * this.topic_num);

                this.topicId_of_corpus[docIndex][wordIndex] = topicId;

                this.word_num_in_doc_topic[docIndex][topicId] += 1;

            }
        }
    }

    private void save_theta() throws IOException {
        BufferedWriter writer = IOUtil.getWriter(this.outputPath + "model-final.theta");
        int doc_index = 0;
        for (int[] line : this.word_num_in_doc_topic) {
            for (int topic : line) {
                writer.write((this.alpha + topic) / (this.word_num_in_doc[doc_index] + this.alpha * this.topic_num) + " ");
//                writer.write(((double)topic) / (this.word_num_in_doc[doc_index]) + " ");
            }
            writer.write("\n");
            doc_index++;
        }
        writer.close();
    }

    private void save_tassign() throws IOException {
        BufferedWriter writer = IOUtil.getWriter(this.outputPath + "model-final.tassign");
        for (int doc_index = 0; doc_index < this.topicId_of_corpus.length; doc_index++) {
            for (int word_index = 0; word_index < this.topicId_of_corpus[doc_index].length; word_index++) {
                writer.write(this.wordId_of_corpus[doc_index][word_index] + ":" + this.topicId_of_corpus[doc_index][word_index] + " ");
            }
            writer.write("\n");
        }
        writer.close();
    }

    private int sample(int docIndex, int wordIndex) {
        int wordId = this.wordId_of_corpus[docIndex][wordIndex];
        int oldTopicId = this.topicId_of_corpus[docIndex][wordIndex];
        int newTopicId = -1;

        this.word_num_in_doc_topic[docIndex][oldTopicId] -= 1;

        double[] p = new double[this.topic_num];
        for (int k = 0; k < this.topic_num; k++) {
            p[k] = (word_num_in_doc_topic[docIndex][k] + alpha) * phi[k][wordId];

        }

        for (int k = 1; k < this.topic_num; k++) {
            p[k] += p[k - 1];
        }

        double u = Math.random() * p[this.topic_num - 1];
        for (int k = 0; k < this.topic_num; k++) {
            if (u < p[k]) {
                newTopicId = k;
                break;
            }

        }

        this.word_num_in_doc_topic[docIndex][newTopicId] += 1;

        assert newTopicId > -1;
        return newTopicId;

    }

    private void build_model() {

        for (int iter = 0; iter < this.iter_num; iter++) {
            long startTime = System.currentTimeMillis();

            for (int docIndex = 0; docIndex < this.wordId_of_corpus.length; docIndex++) {
                for (int wordIndex = 0; wordIndex < this.wordId_of_corpus[docIndex].length; wordIndex++) {
                    this.topicId_of_corpus[docIndex][wordIndex] = this.sample(docIndex, wordIndex);
                }
            }

            System.out.println("finished iter :" + iter + "\tcost time:" + ((double) System.currentTimeMillis() - startTime) / 1000);
        }

    }

    private void save_result() {
        try {
            // this.save_twords(20);
            this.save_theta();
            this.save_tassign();

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void run() {
        this.load_worMap();
        this.load_data();
        this.load_phi();
        this.init_model();
        this.build_model();
        this.save_result();
    }


}

